#include "3ds.h"

Model3DS::Model3DS(Stg::World *world, Stg::Pose pose, std::string fname) : Stg::Model(world, NULL, fname)
{
	this->pose = pose;
	file = 0;

	Stg::Geom g;
	g.size = Stg::Size(0.1, 0.1, 0.1);				// iRat size
	SetGeom(g);

	loadFromFile(const_cast<char*>(fname.c_str()));
}

Model3DS::~Model3DS()
{
	lib3ds_file_free(file);
}

bool Model3DS::loadFromFile(const char * filename)
{
	this->filename = filename;

	cout << "Attempting to load " << this->filename << "...";

	// load the 3ds file
	file = lib3ds_file_open(filename);

	if (!file)
	{
		cout << "could not load file\n";
		return false;
	}

	cout << "done.\n";

	// create nodes for meshes
	if (!file->nodes)
	{
		cout << "File does not have nodes - creating...";
		lib3ds_file_create_nodes_for_meshes(file);
		cout << "done - file->nodes is now " << file->nodes << ".\n";
	}

	Lib3dsNode * node = file->nodes;
	for (;node != NULL; node = node->next)
	{
		node->user_id = 0;
	}

	file->lights = 0;

	// evaluate the file
	cout << "Evaluating file...";
	lib3ds_file_eval(file, 0);
	cout << "done.\n";

	Lib3dsMesh * mesh;

	cout << "File has " << file->nmeshes << " meshes.\n";

	std::string path(filename);
	path.erase(path.begin()+path.rfind('/')+1, path.end());


	for (int i = 0; i < file->nmaterials; i++)
	{
		Lib3dsTextureMap *tex_map= & file->materials[i]->texture1_map;

//		int l = strlen(tex_map->name);
//		tex_map->name[l-1] = 'g';
//		tex_map->name[l-2] = 'n';
//		tex_map->name[l-3] = 'p';

		printf("LOADING TEXTURE: %s WITH materials index=%d ", (path+std::string(tex_map->name)).c_str(), i);

		cv::Mat tex = cv::imread(path+std::string(tex_map->name), -1);

		printf("TEXTURE IS %dx%d:%dchannels ", tex.rows, tex.cols, tex.channels());

		GLuint gl_id = 0; // temporary index to old the place of our texture
		glGenTextures(1, &gl_id); // allocate memory for one texture
		glBindTexture(GL_TEXTURE_2D, gl_id); // use our newest texture

		printf("GL_ID=%d\n", gl_id);

		//gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, (int)pow(mesh->map_planar_size[0], 0.5), //square root of pixels
		//	(int)pow(mesh->map_planar_size[1], 0.5), GL_RGB, GL_FLOAT,
		//	mesh->texelL); // genereate MipMap levels for our texture

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // give the best result for texture magnification
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //give the best result for texture minification
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); // don't repeat texture
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); // don't repeat texture

		if (tex.channels() == 3)
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tex.cols, tex.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, tex.ptr(0));
		else if (tex.channels() == 4)
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.cols, tex.rows, 0, GL_BGRA, GL_UNSIGNED_BYTE, tex.ptr(0));

		tex_map->user_id = gl_id;
	}

	return true;
}

bool Model3DS::createLightList()
{
	lightListIndex = glGenLists(1);
	glNewList(lightListIndex, GL_COMPILE_AND_EXECUTE);
	Lib3dsLight * light; // temporary variable to store our lights
	GLint curlight = GL_LIGHT0;
	for(int i = 0; i < file->nlights; i++)
	{
		light = file->lights[i];

		if(light->off) //if our light is off
		{
			continue; //Move to the next light
		}

		GLfloat diff[4], amb[4], pos[4];

		for(int j = 0;j < 3;j++)
		{
			diff[j] = light->color[j];
			amb[j] = light->color[j] / 4.5; // We might wanna change this;
			pos[j] = light->position[j];
		}

		diff[3] = amb[3] = pos[3] = 1.0;

		glEnable(curlight);
		curlight++;
		// Start setting light
		glLightfv(GL_LIGHT0, GL_DIFFUSE, diff); //set the diffuse color
		glLightfv(GL_LIGHT0, GL_POSITION, pos); //set the position of the light
		glLightfv(GL_LIGHT0, GL_AMBIENT, amb); // set the ambient color of the light
		glLightfv(GL_LIGHT0, GL_SPECULAR, diff); // set the specular color of the light

		if(light->spot_light) // if it's a light spot
		{
			for(int i = 0;i < 3;i++) // get position of the light
			{
				pos[i] = light->target[i] - light->position[i];
			}
			pos[3] = light->target[3] - light->target[3];
			glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, pos); //specify that we have a spot at position 'pos'
		}
	}
	glEndList();

	return true;
}

void Model3DS::listInstructions(Lib3dsMesh * mesh)
{
	unsigned p;
	float (*normals)[3];
	normals = static_cast<float(*)[3]> (std::malloc (20*sizeof(float)*mesh->nvertices)); //alocate memory for our normals
	{
		float m[4][4];
		lib3ds_matrix_copy(m, mesh->matrix); //copy the matrix of the mesh in our temporary matrix
		lib3ds_matrix_inv(m);
		glMultMatrixf(&m[0][0]); //adjust our current matrix to the matrix of the mesh

	}
	lib3ds_mesh_calculate_vertex_normals(mesh, normals); //calculate the normals of the mesh
	int curIndex = 0;

	if(mesh->texcos)
	{
		glEnable(GL_TEXTURE_2D);
		curIndex++;
	}

//	glBindTexture(GL_TEXTURE_2D, file->materials[mesh->faces[0].material]->texture1_map.user_id);
//	printf("bound %d to this mesh\n", file->materials[mesh->faces[0].material]->texture1_map.user_id);

	for(p = 0; p < mesh->nfaces; p++)
	{
		Lib3dsFace *f = &mesh->faces[p];
		Lib3dsMaterial *mat=0;

		GLuint gl_id = file->materials[f->material]->texture1_map.user_id;
		if (glIsTexture(gl_id) == 0)
		{
			printf("no texture\n");
			glDisable(GL_TEXTURE_2D);
			glColor3fv(file->materials[f->material]->ambient);
		}
		else
		{
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, file->materials[f->material]->texture1_map.user_id);
		}

		if(f->material > -1) //if the face of the mesh has material properties
		{
			//int index = lib3ds_file_material_by_name(file, f->material); //read material properties from file
			mat = file->materials[f->material];
		}
		if( false /*mat*/) //if we have material
		{
			static GLfloat ambient[4] = { 0.0, 0.0, 0.0, 1.0 };
			glMaterialfv(GL_FRONT, GL_AMBIENT, ambient); // Ambient color
			glMaterialfv(GL_FRONT, GL_DIFFUSE, mat->diffuse); //diffuse color
			glMaterialfv(GL_FRONT, GL_SPECULAR, mat->specular); //specular color
			float shine;
			shine = pow(2, 10.0 * mat->shininess);
			if(shine > 128.0)
			{
				shine = 128.0;
			}
			glMaterialf(GL_FRONT, GL_SHININESS, shine);
		}
		else // if we do not have material properties, we have to set them manually
		{
/*
			GLfloat diff[4] = { 0.75, 0.75, 0.75, 1.0 }; // color: white/grey
			GLfloat amb[4] = { 0.25, 0.25, 0.25, 1.0 }; //color: black/dark gray
			GLfloat spec[4] = { 0.0, 0.0, 0.0, 1.0 }; //color: completly black
			glMaterialfv(GL_FRONT, GL_DIFFUSE, diff);
			glMaterialfv(GL_FRONT, GL_AMBIENT, amb);
			glMaterialfv(GL_FRONT, GL_AMBIENT, spec);
*/
		}

		{
			float matrix[16];
			glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
			glBegin(GL_TRIANGLES);
			for(int i = 0;i < 3;i++)
			{
				//glTexCoord2fv(mesh->texcos[f->index[i]]);
				glTexCoord2f(mesh->texcos[f->index[i]][0], 1.0-mesh->texcos[f->index[i]][1]);
				glNormal3fv(normals[f->index[i]]); //set normal vector of that point
				glVertex3fv(mesh->vertices[f->index[i]]); //Draw point of the mesh
				//totalVertexCount++;

				//cout << mesh->vertices[f->index[i]][0] << ", " <<
				//	mesh->vertices[f->index[i]][1] <<  ", " <<
				//	mesh->vertices[f->index[i]][2] <<
				//	endl;
			}
			glEnd();
		}
	}
	free(normals); //free up memory
}

bool Model3DS::renderNode(Lib3dsNode * node)
{
	assert(file); //this is for debugging
	{
		Lib3dsNode *tmp;
		for(tmp = node->childs;tmp != 0;tmp = tmp->next)
		{
			renderNode(tmp); //render all child nodes of this note
		}
	}
	if(node->type == LIB3DS_NODE_MESH_INSTANCE) //check wheter the node is a 3ds node
	{
		if(!node->user_id) //Wheter we have a list or not, if not we're gonna create one
		{
			cout << "Generating a list for " << this->filename << "...";
			int index = lib3ds_file_mesh_by_name(file, node->name); //get all the meshes of the current node
			assert(index > -1); //for debugging in case we don't have a mesh

			if(index < 0)
			{
				return false;
			}

			Lib3dsMesh *mesh = file->meshes[index];
			node->user_id = glGenLists(1); //alocate memory for one list
			/////////////////////////////////////////////////////////////
			if(glGetError() != GL_NO_ERROR)
			{
				cout << "ERROR!\n";
				return false;
			}

			int totalVertexCount = 0;

			/////////////////////////////////////////////////////////////
			glNewList(node->user_id, GL_COMPILE); //here we create our list
			{
//				glEnable(GL_CULL_FACE);
				listInstructions(mesh);
			}
			glEndList(); // end of list
			cout << "done with " << totalVertexCount << " vertices.\n";
		}

		if(node->user_id) // if we have created a link list(with glNewList)
		{

			// convert to a mesh instance node
			Lib3dsMeshInstanceNode * min = (Lib3dsMeshInstanceNode*) node;

			glPushMatrix(); //save transformation values

			glShadeModel(GL_SMOOTH);
			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

			glScalef(scale, scale, scale);
			glTranslatef(-min->pivot[0], -min->pivot[1], -min->pivot[2]); //move to the right place;
			glMultMatrixf(&node->matrix[0][0]); //adjust matrix according to the node

			glDisable(GL_COLOR_MATERIAL);

			float matrix[16];
			glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
			glCallList(node->user_id); //render node

			glPopMatrix(); //return transformation original values
		}
	}
	return true;
}

void Model3DS::DrawBlocks()
{
	std::string visible("visible");
	if (!file || ! (int)(GetProperty(visible)))
	{
		return;
	}

	Lib3dsNode *nodes;
	glEnable(GL_TEXTURE_2D);
	glColor4f(1, 1, 1, 1);
	for(nodes = file->nodes; nodes != 0; nodes = nodes->next) // Render all nodes
	{
		renderNode(nodes);
	}
	glDisable(GL_TEXTURE_2D);


	curFrame++;
	if(curFrame > file->frames) //if the next frame doesn't exist, go to frame 0
		curFrame = 0;
	lib3ds_file_eval(file, curFrame); // set current frame
}
